import { NesUtil } from ".";
import Point, { PointI } from "./Point";

export type KeyboardName = "Escape" | "F1" | "F2" | "F3" | "F4" | "F5" | "F6" | "F7" | "F8" | "F9" | "F10" | "F11" | "F12" | "Insert" | "Delete" |
    "Home" | "End" | "PageUp" | "PageDown" | "`" | "Backspace" | "Tab" | "CapsLock" | "Shift" | "Control" | "Meta" | "Alt" | " " | "Enter" | "\\" |
    "ArrowUp" | "ArrowDown" | "ArrowLeft" | "ArrowRight" | "Clear" | "/" | "*" | "-" | "+" | "NumLock" |
    "MediaPlayPause" | "MediaStop" | "MediaTrackPrevious" | "MediaTrackNext" | "LaunchApplication2" | "AudioVolumeUp" | "AudioVolumeDown" | "AudioVolumeMute" |
    "~" | "!" | "@" | "#" | "$" | "%" | "^" | "&" | "(" | ")" | "_" | "{" | "}" | ":" | "\"" | "<" | ">" | "?" | "|" |
    "q" | "w" | "e" | "r" | "t" | "y" | "u" | "i" | "o" | "p" | "a" | "s" | "d" | "f" | "g" | "h" | "j" | "k" | "l" | "z" | "x" | "c" | "v" | "b" | "n" | "m" |
    "Q" | "W" | "E" | "R" | "T" | "Y" | "U" | "I" | "O" | "P" | "A" | "S" | "D" | "F" | "G" | "H" | "J" | "K" | "L" | "Z" | "X" | "C" | "V" | "B" | "N" | "M" |
    "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | ".";

export enum Player {
    A,
    B,
}

export enum Key {
    A,
    B,
    SELECT,
    START,
    UP,
    DOWN,
    LEFT,
    RIGHT,
}

export type PlayerKeyMap = { [player in Player]: { [key in Key]: 0x40 | 0x41 } };

export class Controller {
    public static readonly playerCount = 2;

    public nes: NesUtil;

    public players: PlayerKeyMap;

    constructor(nes: NesUtil) {
        this.nes = nes;

        this.players = new Array(Controller.playerCount)
            .fill(undefined)
            .reduce((players, _info, playerIndex) => {
                players[playerIndex] = new Array(8)
                    .fill(undefined)
                    .reduce((obj, _item, index) => {
                        obj[index] = 0x40;
                        return obj;
                    },{});
                return players;
            }, {});
    }

    public readKeyState(player: Player, key: Key) {
        return this.players[player][key];
    }

    public setKeyState(player: Player, keys: Key[], isDown: boolean) {
        for (const key of keys) {
            this.players[player][key] = isDown ? 0x41 : 0x40;
        }
    }

    public setKeyStateRepeating(player: Player, keys: Key[]) {
        let isRun: boolean = false;
        let state: boolean = true;
        const run = () => {
            this.setKeyState(player, keys, state);
            state = !state;
            this.nes.nexttick(() => {
                if (isRun) {
                    run();
                }
            }, 5);
        };
        return {
            down: () => {
                if(!isRun) {
                    isRun = true;
                    run();
                }
            },
            up: () => {
                isRun = false;
                this.setKeyState(player, keys, false);
            },
        };
    }

    public setSteeringWheel() {
        let isMouseDowm = false;
        const triggerKey = (current: PointI, target: PointI) => {
            if(!isMouseDowm) {
                return;
            }
            let angle = Point.GetAngle(current, target);
            angle = angle + 45 / 2;
            angle %= 360;
            const keys: Set<Key> = new Set();
            if (angle < 45) {
                keys.add(Key.RIGHT);
            }
            else if (angle < 95) {
                keys.add(Key.RIGHT);
                keys.add(Key.DOWN);
            }
            else if (angle < 135) {
                keys.add(Key.DOWN);
            }
            else if (angle < 180) {
                keys.add(Key.LEFT);
                keys.add(Key.DOWN);
            }
            else if (angle < 225) {
                keys.add(Key.LEFT);
            }
            else if (angle < 270) {
                keys.add(Key.LEFT);
                keys.add(Key.UP);
            }
            else if (angle < 315) {
                keys.add(Key.UP);
            }
            else if (angle < 360) {
                keys.add(Key.RIGHT);
                keys.add(Key.UP);
            }
            [Key.UP, Key.DOWN, Key.LEFT, Key.RIGHT].forEach(key => {
                this.setKeyState(Player.A, [key], keys.has(key));
            });
        };

        const getIsMouseDowm = () => {
            return isMouseDowm;
        };
        const down = (current: PointI, target: PointI) => {
            isMouseDowm = true;
            triggerKey(current, target);
        };
        const up = () => {
            isMouseDowm = false;
            this.setKeyState(Player.A, [Key.UP, Key.DOWN, Key.LEFT, Key.RIGHT], false);
        };
        const move = (current: PointI, target: PointI) => {
            triggerKey(current, target);
        };

        return { getIsMouseDowm, down, move, up };
    }
}